package j.util.web;

import j.data.ActionResult;
import j.framework.core.ServiceException;
import j.util.New;
import j.util.ResubmitUtils;
import j.util.StringUtils;
import j.util.exception.ApplicationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 所有控制器的基类
 * User: jason
 * Date: 13-4-22 下午1:33
 */
public abstract class BaseController extends HttpServlet {

    public static final String NO_PRIVILEGE_MESSAGE_FOR_ROLE = "you must have the %s role to visit.%s";
    public static final String NO_PRIVILEGE_MESSAGE_FOR_FUNCTION = "you must have the %s function to visit.%s";
    public static final String NO_PRIVILEGE_MESSAGE_FOR_ACTION = "you must have the %s function's %s action to do this.%s";

    public static final String HTTP_POST_TYPE_HEADER = "HTTP_POST_TYPE_HEADER";
    public static final String HTTP_POST_TYPE_HEADER_VALUE = "ajax";
    public static final String RESUBMIT_MESSAGE_INFO = "本次请求系统检测到为重复提交，系统将不予执行，请不要点击按钮一次以上。";

    private Map<String,String> actionMapper = New.map();

    protected void setActionMapper(Map<String,String> mapper){}

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doService(req,resp,true);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doService(req,resp,false);
    }

    /**
     * spring beans的托管容器
     */
    protected WebApplicationContext wac;
    /**
     * 日志记录器
     */
    protected Log log = LogFactory.getLog(this.getClass());

    @Override
    public void init() throws ServletException {
        super.init();
        wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        //实例化该控制器中所定义的bean
        Field[] fields = this.getClass().getDeclaredFields();
        ApplicationBean ab;
        String abId;
        Object v;
        for(Field field : fields){
            ab = field.getAnnotation(ApplicationBean.class);
            if(null!=ab){
                abId = ab.id();
                if(StringUtils.isNotEmpty(abId)){
                    v = wac.getBean(abId);
                }else{
                    v = wac.getBean(field.getType());
                }
                if(null!=v){
                    field.setAccessible(true);
                    try {
                        field.set(this,v);
                    } catch (IllegalAccessException e) {
                        throw new ApplicationException("类型为[%s]的bean无法注入到servlet中.",field.getType());
                    }
                }
            }
        }

        //解析注解action映射
        Method[] methods = this.getClass().getDeclaredMethods();
        WebRequest webRequest;
        for(Method m : methods){
            webRequest = m.getAnnotation(WebRequest.class);
            if(null!=webRequest){
                if(StringUtils.isNotEmpty(webRequest.action())){
                    actionMapper.put(webRequest.action().toLowerCase(),m.getName());
                }
            }
        }
        //设置actiogn与操作方法的映射，该映射将替换掉注解的映射
        setActionMapper(actionMapper);
    }

    /**
     * 从spring 容器中获取指定类型的bean
     */
    protected <T> T getBean(Class<T> clazz){
        return wac.getBean(clazz);
    }

    /**
     * 从spring容器中按照beanName及bean类型获取bean
     * @param beanName 指定的bean名称
     * @param clazz  bean的类型
     */
    protected <T> T getBean(String beanName,Class<T> clazz){
        return wac.getBean(beanName,clazz);
    }

    private void doService(HttpServletRequest req, HttpServletResponse resp,boolean isPost) throws ServletException, IOException {
        String act = ParamUtils.getAct(req, null);
        String methodName;
        if(StringUtils.isNullOrEmpty(act)){
            methodName = isPost ? "doDefaultPost" : "doDefaultGet";
        }else{
            methodName = actionMapper.get(act);
        }
        ExceptionType et = ExceptionType.NORMAL;
        boolean neededToClearToken = false;
        //用反射技术调用相对相对应的方法
        try {
            Method m = getClass().getDeclaredMethod(methodName,HttpView.class);
            if(null == m){
                throw new ApplicationException("控制器中没有找到[%s]对应的操作方法",act);
            }
            //先处理WebRequest注解
            WebRequest webRequest = m.getAnnotation(WebRequest.class);
            if(webRequest!=null){
                //获取请求方式
                et = webRequest.exceptionType();
                //先检测请求方式是否正确
                if(!webRequest.method().equals(RequestMethod.ANY)){
                    if(isPost && webRequest.method().equals(RequestMethod.GET)){
                        throw new ApplicationException("this request method is not supported.");
                    }
                    if(!isPost && webRequest.method().equals(RequestMethod.POST)){
                        throw new ApplicationException("this request method is not supported.");
                    }
                }
                //再检测该请求是否启用防重复提交
                if(webRequest.duplicate()){
                    neededToClearToken = true;
                    //开启了防重复提交功能
                    boolean b = ResubmitUtils.isValid(req);
                    String postType = req.getHeader(HTTP_POST_TYPE_HEADER);
                    /*if(b && !HTTP_POST_TYPE_HEADER_VALUE.equals(postType)){
                        //如果不是重复提交且不是ajax请求，则清空令牌
                        ResubmitUtils.clearToken(request);//清空令牌
                    }*/
                    if(!b){
                        if(null != postType && postType.equals(HTTP_POST_TYPE_HEADER_VALUE)){
                            //ajax请求，以json格式返回
                            ActionResult ar = ActionResult.New();
                            ar.add(RESUBMIT_MESSAGE_INFO);
                            resp.setContentType("text/plain");
                            resp.setCharacterEncoding("utf8");
                            PrintWriter out = resp.getWriter();
                            out.write(ar.toString());
                            out.flush();
                            out.close();
                            //运行至此结束
                            return;
                        }else{
                            //传统post请求，直接抛出异常，由错误处理页面处理错误
                            throw new ApplicationException(RESUBMIT_MESSAGE_INFO);
                        }
                    }
                }
            }
            HttpView httpView = new HttpView(req,resp,neededToClearToken);
            m.invoke(this,httpView);
        } catch (Exception e) {
            String msg = null;
            Throwable t = e.getCause();
            if(t instanceof ServiceException){
                msg = t.getMessage();
            }else if(null!=t){
                msg = t.getMessage();
                if(StringUtils.isNullOrEmpty(msg)) {
                    while (null != t && StringUtils.isNullOrEmpty(msg)) {
                        t = t.getCause();
                        if (t != null) {
                            msg = t.getMessage();
                        }
                    }
                }
            }
            if(msg == null){
                msg = "服务器发生未知错误，请稍后再试！";
            }
            e.printStackTrace();
            processErrors(req,resp,et,new ApplicationException(msg),neededToClearToken);
        }
    }

    private void processErrors(HttpServletRequest req,HttpServletResponse resp,ExceptionType et,ApplicationException e,boolean neededToClearToken) throws ServletException, IOException{
        if(et.equals(ExceptionType.NORMAL)){
            String ajaxHeader = req.getHeader("HTTP_AJAX_POST_HEADER");
            if(StringUtils.isNotEmpty(ajaxHeader)){
                //这是ajax请求
                resp.setStatus(500);
                HttpView.toOut(req,resp,e.getMessage());
            }else{
                throw e;
            }
        }else if(et.equals(ExceptionType.TARGET_IFRAME)){
            String err = String.format("<ul><li>%s</li></ul>",e.getMessage());
            HttpView.toOut(req, resp, String.format("<script>parent.f.setError(\"%s\");</script>", err));
        }else if(et.equals(ExceptionType.AJAX)){
            ActionResult ar = ActionResult.New();
            ar.add(e.getMessage());
            HttpView.toClient(req, resp, ar, null, neededToClearToken);
        }
    }



    public abstract void doDefaultGet(HttpView view) throws ServletException, IOException;
    public void doDefaultPost(HttpView view) throws ServletException, IOException{}
}
